﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using CSJ2K.Jpc.Segments;
using CSJ2K.Util.Collections.Generic;
using CSJ2K.Util.IO;

namespace CSJ2K.Jpc
{
    public class CodeStream
    {
        public int Layers
        {
            get
            {
                return Cod.SG.B;
            }
        }
        public byte Resolutions
        {
            get
            {
                return (byte)(Cod.SP.D + 1);
            }
        }
        public byte DecompositionLevels
        {
            get
            {
                return Cod.SP.D;
            }
        }
        public int Components
        {
            get
            {
                return Siz.C;
            }
        }
        public COD.ProgressionOrders Progressions
        {
            get
            {
                return Cod.SG.A;
            }
        }

        public RasterDictionary<Tile> Tiles;

        public SOC Soc;                         // Required as the first marker
        public SIZ Siz;                         // Required as the second marker segment
        public COD Cod;                         // Required
        public List<COC> Coc = new List<COC>(); // Optional; no more than one COC per component
        public QCD Qcd;                         // Required
        public List<QCC> Qcc = new List<QCC>(); // Optional; no more than one QCC per component
        public List<RGN> Rgn = new List<RGN>(); // Optional; no more than one RGN per component
        public List<POC> Poc = new List<POC>(); // Optional; Required in main or tile for any progression order changes
        public PPM Ppm;                         // Optional; either PPM or PPT or codestream packet headers required
        public TLM Tlm;                         // Optional
        public PLM Plm;                         // Optional
        public CRG Crg;                         // Optional
        public List<COM> Com = new List<COM>(); // Optional
        public EOC Eoc;

        public uint numXtiles;
        public uint numYtiles;

        private bool ReadSOTMarker = false;

        public CodeStream(string Filename)
        {
            Stream Source = (Stream)new FileStream(Filename, FileMode.Open, FileAccess.Read);
            ReadCodeStream(Source);
        }
        public CodeStream(Stream Source)
        {
            ReadCodeStream(Source);
        }
        private void ReadCodeStream(Stream Source)
        {
            Debug.WriteLine("Reading Header");
            Debug.Indent();
            if (ReadHeader(Source))
            {
                Debug.Unindent();
                Debug.WriteLine("Reading Tile-Parts");
                Debug.Indent();
                if (!ReadTileParts(Source)) Debug.WriteLine("Stream ended before EOC");
                Debug.Unindent();
            }
            else
            {
                Debug.WriteLine("Stream ended before SOT");
                Debug.Unindent();
            }
        }
        private bool ReadHeader(Stream source)
        {
            Debug.WriteLine("SOC");
            Soc = new SOC(source);

            Debug.WriteLine("SIZ");
            Debug.Indent();
            Siz = new SIZ(source);
            Debug.Unindent();

            Segment.Markers marker;
            while (true)
            {
                try { marker = (Segment.Markers)StreamUtil.ReadBUInt16(source); }
                catch (EndOfStreamException) { return (false); }
                if (marker != Segment.Markers.SOT) Debug.WriteLine(marker.ToString());
                Debug.Indent();
                switch (marker)
                {
                    case Segment.Markers.COD:
                        if (Cod != null) throw(new ApplicationException("Too many COD marker segments in header."));
                        Cod=new COD(source, marker);
                        break;
                    case Segment.Markers.COC: // Multiple
                        Coc.Add(new COC(source, marker, Components));
                        break;
                    case Segment.Markers.QCD:
                        if (Qcd != null) throw(new ApplicationException("Too many QCD marker segments in header."));
                        Qcd = new QCD(source, marker);
                        break;
                    case Segment.Markers.QCC: // Multiple
                        Qcc.Add(new QCC(source, marker, Components));
                        break;
                    case Segment.Markers.RGN: // Multiple
                        Rgn.Add(new RGN(source, marker));
                        break;
                    case Segment.Markers.POC: // Multiple
                        Poc.Add(new POC(source, marker));
                        break;
                    case Segment.Markers.PPM:
                        if (Ppm != null) throw(new ApplicationException("Too many PPM marker segments in header."));
                        Ppm = new PPM(source, marker);
                        break;
                    case Segment.Markers.TLM:
                        if (Tlm != null) throw(new ApplicationException("Too many TLM marker segments in header."));
                        Tlm = new TLM(source, marker);
                        break;
                    case Segment.Markers.PLM:
                        if(Plm!=null) throw(new ApplicationException("Too many PLM marker segments in header."));
                        Plm = new PLM(source, marker);
                        break;
                    case Segment.Markers.CRG:
                        if(Crg!=null) throw(new ApplicationException("Too many CRG marker segments in header."));
                        Crg = new CRG(source, marker);
                        break;
                    case Segment.Markers.COM: // Multiple
                        Com.Add(new COM(source, marker));
                        break;

                    case Segment.Markers.SOT:
                        ReadSOTMarker=true;
                        Debug.Unindent();
                        return (true);
                    default:
                        throw(new ApplicationException("Unexpected marker 0x" + marker.ToString("X") + " in codestream header."));
                }
                Debug.Unindent();
            }
        }
        private bool ReadTileParts(Stream Source)
        {
            if (Tiles == null)
            {
                // Number of tiles in this image
                numXtiles = (Siz.X - Siz.XTO + Siz.XT - 1) / Siz.XT;
                numYtiles = (Siz.Y - Siz.YTO + Siz.YT - 1) / Siz.YT;

                Tiles = new RasterDictionary<Tile>(numXtiles);
                for (uint i = 0; i < numXtiles * numYtiles; i++) Tiles[i] = new Tile(this, i);
            }

            while (true)
            {
                Segment.Markers marker = Segment.Markers.SOT;

                try
                {
                    if (!ReadSOTMarker) marker = (Segment.Markers)StreamUtil.ReadBUInt16(Source);
                    else ReadSOTMarker = false;
                }
                catch (EndOfStreamException) { return (false); }
        
                if (marker == Segment.Markers.SOT)
                {
                    TilePart tilepart = new TilePart(this, Source, marker);
                    Tiles[tilepart.Sot.I].TileParts.Add(tilepart);
                }
                else if (marker == Segment.Markers.EOC)
                {
                    Eoc = new EOC(Source, marker);
                    Debug.Unindent();
                    Debug.WriteLine("End of Codestream");
                    Debug.Indent();
                    return (true);
                }
                else throw (new ApplicationException("Unexpected marker " + marker.ToString("X")));
            }
        }
    }
}
